/*
============================================================================
 Name        : dd_player.c
 Author      : Jian
 Version     :
 Copyright   : GPL
 Description : dd_player.c - source file
============================================================================
*/

/* INCLUDE FILES */
#include "dd_player.h"
#include "dd_log.h"
#include "dd_ini_reader.h"
#include "dd_util.h"
#include "dd_msg_queue.h"
#include "dd_audio_file.h"
#include "dd_local_audio_file.h"
#include "dd_remote_audio_file.h"
#include "dd_fixed_queue.h"
#include "dd_player_config.h"

#include "sd_platform.h"

#define __READ_QUEUE_COUNT      2
#define __FETCH_INFO_TIME_OUT   5
#define __READING_TIME_OUT      5

#define __TIMER_WAKEUP          1
#define __AUDIO_

enum {
    EStoped = 0,
    EFetchingInfo,
    EPlaying,
    EWaiting,       /* when the pcm buffer queue is empty OR audio devices buffer is full */
    EPaused, 
    EError,
    EStateCount
};

typedef struct {
    DD_UINT         state ;
    
    dd_audio_file*  audioFile ;
    dd_msg_queue*   msgQueue ;
    sd_audio*       audioDevice ;
    sd_timer*       timer ;
    dd_sync_tag     syncTag ;
    
    int             fecthInfoDone ;

    dd_fixed_queue* pcmBufQueue ;   /* pcm buf queue that is NOT sent to audio devices yet */
    dd_fixed_queue* audioBufQueue ; /* pcm buf queue that is sent to audio devices before */
    dd_fixed_queue* freeBufQueue ;  /* free buf queue */
} dd_player;

static int        _isEnvOK    = 0 ;
static dd_player* _player     = NULL ;
static const char* stateStr[] = {"stop", "info", "play", "buffering", "pause", "error"} ;

#define _change_state(s) do {__DD_ASSERT_DEFENSE(((DD_UINT)(s)) < EStateCount, break); \
    dd_log_keypath("change player state from %s to %s in %s, %d", stateStr[_player->state], stateStr[s], __FILE__, __LINE__); _player->state = s;} while(0) 

#define _is_playing(s) ((s) >= EFetchingInfo && (s) <= EWaiting)


static int _player_init() ;
static int _decode_pcm(dd_queue_value* val) ;

void dd_env_init() 
{
    dd_player* player = NULL ;

    sd_env_init() ;
    dd_ini_reload() ;
    dd_config_init() ;
    dd_log_init() ;
    dd_util_init() ;
    
    _player_init() ;
    
    _isEnvOK = (_player != NULL) ;

    dd_log_keypath("init player env is %s", _isEnvOK ? "succeed" : "failed") ;
}

int dd_env_is_ready() 
{
    return _isEnvOK && _player;
}

static void _timer_fun(void* ptr)
{
    __DD_ASSERT_DEFENSE(dd_env_is_ready(), return) ;
    
    dd_mqueue_send(_player->msgQueue, DD_PLY_MSG_WAKEUP, NULL, 0, 0) ;
}

static void _audio_callback(dd_sync_tag tag)
{
    __DD_ASSERT_DEFENSE(dd_env_is_ready(), return) ;

    dd_mqueue_send(_player->msgQueue, DD_PLY_MSG_DATA_PLAYED, &tag, sizeof(tag), 0) ;
}

static int _player_init()
{
    int        i = 0 ;
    dd_player* player = (dd_player*)dd_malloc(sizeof(dd_player)) ;

    __DD_CHECK_MEM(player, sizeof(dd_player), return -1) ;
    
    player->msgQueue = dd_mqueue_create(dd_free) ;
    __DD_ASSERT_DEFENSE(player->msgQueue, return -1) ;
    
    player->audioDevice = sd_audio_create() ;
    __DD_ASSERT_DEFENSE(player->audioDevice, return -1) ;

    player->timer = sd_timer_create() ;
    
    player->pcmBufQueue = dd_fqueue_create(ddPlayerOutBufQueueCount, 0) ;
    __DD_ASSERT_DEFENSE(player->pcmBufQueue, return -1) ;

    player->freeBufQueue = dd_fqueue_create(ddPlayerOutBufQueueCount, 0) ;
    __DD_ASSERT_DEFENSE(player->freeBufQueue, return -1) ;

    player->freeBufQueue = dd_fqueue_create(ddPlayerOutBufQueueCount, 0) ;
    __DD_ASSERT_DEFENSE(player->freeBufQueue, return -1) ;

    for (i = 0; i < ddPlayerOutBufQueueCount; i++) {
        void* buf = dd_malloc(ddPlayerOutBufSize + sizeof(dd_pcm_buffer)) ;
        __DD_CHECK_MEM(buf, ddPlayerOutBufSize, return -1) ;

        if (dd_fqueue_enqueue(player->freeBufQueue, buf) < 0) 
            __DD_ASSERT_DEFENSE(DD_FALSE, return -1) ;
    }

    player->state = EStoped ;
    
    _player = player ;

    return 0;
}


static int _parse_id(const char* fileAttr) 
{
    const char* sep = NULL ;
    int         id = -1 ;
    int         ret = -1 ;

    sep = strchr(fileAttr, DD_FILE_ATTR_SEP) ;
    /* -1 is too big to reach */
    __DD_ASSERT_DEFENSE(sep, return -1) ;

    ret = dd_atoi(sep + 1, &id) ;
    __DD_ASSERT_DEFENSE(ret >= 0, return -1) ;

    return id ;
}

dd_audio_file* _audio_file_create(const char* fileAttr) 
{
    const char* sep = NULL ;
    int         id = 0 ;
    
    id = _parse_id(fileAttr) ;

    if (id == -1) {
        dd_log_error("error to paser id form fileAttr: %s", fileAttr) ;
        return NULL ;
    }

    sep = strchr(fileAttr, DD_FILE_ATTR_SEP) ;

    if (memcmp(fileAttr, DD_LOCAL_FILE_HEAD, strlen(DD_LOCAL_FILE_HEAD)) == 0)
        return dd_lafile_create(id) ;

    if (memcmp(fileAttr, DD_REMOTE_FILE_HEAD, strlen(DD_REMOTE_FILE_HEAD)) == 0)
        return dd_rafile_create(id) ;
    
    dd_log_error("error to paser file type form fileAttr: %s", fileAttr) ;

    return NULL ;
}

static void _do_stop() 
{
    void* val = NULL ;

    _change_state(EStoped) ;
    
    dd_stag_set(&_player->syncTag, 0, 0) ;
    
    if (_player->audioDevice)
        sd_audio_stop(_player->audioDevice) ;
    
    if (_player->audioFile)
        dd_afile_destroy(_player->audioFile) ;
    _player->audioFile = NULL ;
    
    if (_player->timer)
        sd_timer_stop(_player->timer) ;

    while(val = dd_fqueue_dequeue(_player->audioBufQueue))
        dd_fqueue_enqueue(_player->freeBufQueue, val) ;
    
    while(val = dd_fqueue_dequeue(_player->pcmBufQueue)) 
        dd_fqueue_enqueue(_player->freeBufQueue, val) ;
    
    dd_mqueue_reset(_player->msgQueue) ;
}

static int _do_read_info()
{
    int ret = -1 ;
    const dd_audio_file_info* info = NULL ;

    _change_state(EFetchingInfo) ;
    
    ret = dd_afile_info(_player->audioFile, &info) ;

    if (ret == EDDAgain) {
        sd_timer_stop(_player->timer) ;
        sd_timer_start(_player->timer, __FETCH_INFO_TIME_OUT, NULL, _timer_fun) ;
        return 0;
    } 

    if (ret < 0) {
        dd_log_error("dd_afile_info got a error: %d", ret) ;
        _change_state(EError) ;
        return 0;
    }

    _player->fecthInfoDone = 1 ;
    _change_state(EPlaying) ;
    sd_audio_start(_player->audioDevice, _audio_callback, _player->syncTag) ;

    if (dd_fqueue_count(_player->freeBufQueue) == ddPlayerOutBufQueueCount) {
        /* try to full-fill the pcm queue in the beginning */
        int i = 0 ;
        for (; i < ddPlayerOutBufQueueCount; i++) {
            if (!dd_fqueue_try_dequeue(_player->freeBufQueue, _decode_pcm))
                break ;
        }
    }

    return 1 ;
}

static int _do_reusme(const char* fileAttr)
{
    __DD_ASSERT_DEFENSE(_player->audioFile, return 0) ;
    __DD_ASSERT_DEFENSE(_parse_id(fileAttr) == dd_afile_id(_player->audioFile), return 0) ;
    
    if (_player->fecthInfoDone) {
        _change_state(EPlaying) ;
        sd_audio_start(_player->audioDevice, _audio_callback, _player->syncTag) ;
    } else {
        _do_read_info() ;
    }

    return 1 ;
}

static void _handle_play_msg(const char* fileAttr) 
{
    __DD_ASSERT_DEFENSE(fileAttr, return) ;
    
    if (_player->state == EPaused && _do_reusme(fileAttr)) 
        return ;

    if (_player->state != EStoped)
        _do_stop() ;

    _player->fecthInfoDone = 0 ;

    _player->audioFile = _audio_file_create(fileAttr) ;
    if (!_player->audioFile) {
        _change_state(EError) ;
        return ;
    }

    dd_stag_set(&_player->syncTag, time(NULL), dd_afile_id(_player->audioFile)) ;

    _do_read_info() ;
}

static void _handle_pause_msg() 
{
    __DD_ASSERT_DEFENSE(_is_playing(_player->state) , return) ;

    sd_audio_pause(_player->audioFile) ;

    _change_state(EPaused) ;
}

static void _handle_stop_msg() 
{
    __DD_ASSERT_DEFENSE(_player->state != EStoped, return) ;
    _do_stop() ;
}

static void _handle_set_position_msg(DD_UINT second) 
{
    int ret = -1 ;
    dd_queue_value* val = NULL ;
    
    /* set position will cancel the fetching info logic */
    _player->fecthInfoDone = 1 ;
    
    ret = dd_afile_seek(_player->audioFile, second) ;
    if (ret < 0) {
        dd_log_error("got error: %d from dd_afile_seek", ret) ;
        return ;
    }

    sd_audio_stop(_player->audioDevice) ;

    sd_timer_stop(_player->timer) ;

    while(val = dd_fqueue_dequeue(_player->audioBufQueue))
        dd_fqueue_enqueue(_player->freeBufQueue, val) ;

    while(val = dd_fqueue_dequeue(_player->pcmBufQueue)) 
        dd_fqueue_enqueue(_player->freeBufQueue, val) ;
}

static void _handle_wakeup_msg() 
{
    /* do nothing yet */
}

static void _handle_data_played_msg(const dd_sync_tag* tag) 
{
    dd_queue_value* val = NULL ;

    if (!_is_playing(_player->state) && _player->state == EPaused)
        return ;

    if (!dd_stag_is_equal(tag, &_player->syncTag)) {
        dd_log_warn("got a play over tag is not equal to _player's tag") ;
        return ;
    }
    
    __DD_ASSERT_DEFENSE(dd_fqueue_count(_player->freeBufQueue) + dd_fqueue_count(_player->pcmBufQueue) + dd_fqueue_count(_player->audioBufQueue) == DD_PLAYER_OUTPUT_QUEUE_COUNT, return) ;

    val = dd_fqueue_dequeue(_player->audioBufQueue) ;
    __DD_ASSERT_DEFENSE(val, return) ;

    dd_fqueue_enqueue(_player->freeBufQueue, val) ;

    if (_player->state == EWaiting)
        _change_state(EPlaying) ;

    if (_player->state == EPlaying && dd_afile_is_eof(_player->audioFile) && dd_fqueue_count(_player->freeBufQueue) == DD_PLAYER_OUTPUT_QUEUE_COUNT) 
        _do_stop() ;
}

static void _handle_msg(DD_UINT id, void* data, DD_UINT len)
{
    __DD_ASSERT_DEFENSE(_player, return) ;

    switch(id)
    {
    case DD_PLY_MSG_PLAY:
        _handle_play_msg((const char*)data) ;
        break ;

    case DD_PLY_MSG_PAUSE:
        _handle_pause_msg() ;
        break ;

    case DD_PLY_MSG_STOP:
        _handle_stop_msg() ;
        break ;

    case DD_PLY_MSG_SET_POSITION:
        _handle_set_position_msg((DD_UINT)data) ;
        break; 

    case DD_PLY_MSG_WAKEUP:
        _handle_wakeup_msg() ;
        break ;

    case DD_PLY_MSG_DATA_PLAYED:
        _handle_data_played_msg((const dd_sync_tag*)data) ;
        break; 

    default:
        __DD_ASSERT_DEFENSE(DD_FALSE, dd_log_error("not handle player msg: %d", id)) ;
        break;
    }
}

static int _decode_pcm(dd_queue_value* val) 
{
    dd_pcm_buffer* buffer = (dd_pcm_buffer*)val ;
    int            ret = -1 ;

    __DD_ASSERT_DEFENSE(buffer, return 1) ;

    buffer->len = ddPlayerOutBufSize ;
    ret = dd_afile_decode(_player->audioFile, buffer) ;
    
    if (__likely(ret >= 0)) {
        dd_fqueue_enqueue(_player->pcmBufQueue, buffer) ;
        return 1 ;
    } else {
        if (ret != EDDAgain && ret != EEileEOF) {
            dd_log_error("got error: %d from dd_afile_decode function", ret) ;
            _change_state(EError) ;
        }
        
        if (!dd_afile_is_eof(_player->audioFile) && dd_fqueue_count(_player->pcmBufQueue) == 0)
            _change_state(EWaiting) ;

        return 0 ;
    } 
}   

static int _write_to_audio(dd_queue_value* val) 
{
    dd_pcm_buffer* buffer = (dd_pcm_buffer*)val ;
    
    __DD_ASSERT_DEFENSE(buffer, return 1) ;

    if (sd_audio_write_buf(_player->audioDevice, buffer->data, buffer->len) < 0) {
        _change_state(EWaiting) ;
        return 0 ;
    }
    
    dd_fqueue_enqueue(_player->audioBufQueue, val) ;
    
    if (_player->state == EWaiting)
        _change_state(EPlaying) ;

    return 1;
}

int dd_player_run() 
{
    int i = 0;
    int ret = -1;
    
    dd_log_keypath("player is running") ;

    __DD_ASSERT_DEFENSE(dd_env_is_ready(), return -1) ;

    while(DD_TRUE) {
        
        __DD_ASSERT_DEFENSE(_player, break) ;
        __DD_ASSERT_DEFENSE(_player->state < EStateCount, _change_state(EError)) ;

        for(i = 0; i < __READ_QUEUE_COUNT; i++) {
            if (__likely(_player->state == EPlaying))
                ret = dd_mqueue_recv(_player->msgQueue, _handle_msg, 0) ;
            else 
                ret = dd_mqueue_recv(_player->msgQueue, _handle_msg, EMQFlagWait) ;
            
            if (__likely(ret < 0))
                break ;

            if (__unlikely(_player->state == EError))
                break ;
        }

        if (__unlikely(!_is_playing(_player->state)))
            continue ;

        if (__unlikely(_player->state == EFetchingInfo)) 
            _do_read_info() ;

        if (__likely(_player->state == EPlaying) || (_player->state == EWaiting)) {

            if (!dd_afile_is_eof(_player->audioFile) && dd_fqueue_count(_player->freeBufQueue) > 0) {  
                /* TODO: read more when the PCM queue is small and audio queue is big */
                dd_fqueue_try_dequeue(_player->freeBufQueue, _decode_pcm) ;
            }
        }

        if (__likely(_player->state == EPlaying) || _player->state == EWaiting) {
            while(dd_fqueue_try_dequeue(_player->pcmBufQueue, _write_to_audio)) 
                continue ; 
        }
    }
    
    __DD_ASSERT_DEFENSE(DD_FALSE, _isEnvOK = 0) ; /* goto here mean that we got an fatal error, just stop everything */

    return 0 ;
}

int dd_player_play(const char* file) 
{
    __DD_ASSERT_DEFENSE(dd_env_is_ready(), return -1) ;

    dd_mqueue_send(_player->msgQueue, DD_PLY_MSG_PLAY, (void*)file, strlen(file) + 1, EMQFlagWait) ;
}

int dd_player_pause() 
{
    __DD_ASSERT_DEFENSE(dd_env_is_ready(), return -1) ;

    dd_mqueue_send(_player->msgQueue, DD_PLY_MSG_PAUSE, NULL, 0, EMQFlagWait) ;
}

int dd_player_stop() 
{
    __DD_ASSERT_DEFENSE(dd_env_is_ready(), return -1) ;

    dd_mqueue_send(_player->msgQueue, DD_PLY_MSG_STOP, NULL, 0, EMQFlagWait) ;
}

int dd_player_set_position(DD_UINT second) 
{
    __DD_ASSERT_DEFENSE(dd_env_is_ready(), return -1) ;

    dd_mqueue_send(_player->msgQueue, DD_PLY_MSG_SET_POSITION, (void*)second, 0, EMQFlagWait) ;
}

/* End of File */
